package com.xu.cloud.common.advice;

import com.xu.cloud.common.advice.annotation.ReqDecrypt;
import com.xu.cloud.common.constant.CodecConstant;
import com.xu.cloud.common.constant.CryptConstant;
import com.xu.cloud.common.constant.OrderConstant;
import com.xu.cloud.common.pojo.http.req.EncryptRequest;
import com.xu.cloud.common.processor.DecryptionConfigurationPropertyHelper;
import com.xu.cloud.common.util.JsonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.Objects;

/**
 * 请求体解密后置通知处理器
 *
 * @author xuguan
 * @since 2024/9/2
 */
@Order(OrderConstant.REQUEST_BODY_DECRYPT_ADVICE_ORDER)
@RestControllerAdvice
public class RequestBodyDecryptAdvice extends RequestBodyAdviceAdapter {

    @Autowired
    private Environment environment;

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return methodParameter.hasMethodAnnotation(ReqDecrypt.class)
                || Objects.nonNull(AnnotationUtils.findAnnotation(methodParameter.getContainingClass(), ReqDecrypt.class));
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        final String encryptionKey = environment.getProperty(CryptConstant.ENCRYPTION_KEY_PROPERTY_NAME);
        if (encryptionKey == null) {
            throw new IllegalArgumentException("encryption.key is not set");
        }

        final String body = StreamUtils.copyToString(inputMessage.getBody(), CodecConstant.UTF_8);
        if (CodecConstant.EMPTY_JSON.equals(body)) {
            return new DecryptHttpInputMessage(new ByteArrayInputStream(body.getBytes(CodecConstant.UTF_8)), inputMessage.getHeaders());
        }

        final EncryptRequest encryptRequest = JsonUtil.parseObj(body, EncryptRequest.class);
        final String decryptionBody = DecryptionConfigurationPropertyHelper.decryptionProperty(encryptRequest.getEncryptData(), encryptionKey);
        return new DecryptHttpInputMessage(new ByteArrayInputStream(decryptionBody.getBytes(CodecConstant.UTF_8)), inputMessage.getHeaders());
    }

    private static class DecryptHttpInputMessage implements HttpInputMessage {
        private final InputStream body;
        private final HttpHeaders headers;

        private DecryptHttpInputMessage(InputStream body, HttpHeaders headers) {
            this.body = body;
            this.headers = headers;
        }

        @Override
        public InputStream getBody() throws IOException {
            return body;
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }
    }

}
